// setup实现


// 任务缓存队列，用一个 Set 数据结构来表示，这样就可以自动对任务进行去重
const queue = new Set()
// 一个标志，代表是否正在刷新任务队列
let isFlushing = false
// 创建一个立即 resolve 的 Promise 实例
const p = Promise.resolve()

// 调度器的主要函数，用来将一个任务添加到缓冲队列中，并开始刷新队列
function queueJob(job) {
    // 将 job 添加到任务队列 queue 中
    queue.add(job)
    // 如果还没有开始刷新队列，则刷新之
    if (!isFlushing) {
        // 将该标志设置为 true 以避免重复刷新
        isFlushing = true
        // 在微任务中刷新缓冲队列
        p.then(() => {
            try {
                // 执行任务队列中的任务
                queue.forEach(job => job())
            } finally {
                // 重置状态
                isFlushing = false
                queue.clear = 0
            }
        })
    }
}

// 全局变量，存储当前正在被初始化的组件实例
let currentInstance = null
// 该方法接收组件实例作为参数，并将该实例设置为 currentInstance
function setCurrentInstance(instance) {
    currentInstance = instance
}

function onMounted(fn) {
    if (currentInstance) {
        // 将生命周期函数添加到 instance.mounted 数组中
        currentInstance.mounted.push(fn)
    } else {
        console.error('onMounted 函数只能在 setup 中调用')
    }
}

function resolveProps(options, propsData) {
    const props = {}
    const attrs = {}
    for (const key in propsData) {
        // 以字符串 on 开头的 props，无论是否显式地声明，都将其添加到 props 数据中，而不是添加到 attrs 中
        if (key in options || key.startsWith('on')) {
            props[key] = propsData[key]
        } else {
            attrs[key] = propsData[key]
        }
    }

    return [props, attrs]
}


function mountComponent(vnode, container, anchor) {
    const componentOptions = vnode.type
    // 从组件选项中取出 setup 函数
    let {
        render,
        data,
        setup,
        props: propsOption,
        beforeCreate,
        created,
        beforeMount,
        mounted,
        beforeUpdate,
        updated
    } = componentOptions

    beforeCreate && beforeCreate()

    const state = data ? reactive(data()) : null
    // 调用 resolveProps 函数解析出最终的 props 数据与 attrs 数据
    const [props, attrs] = resolveProps(propsOption, vnode.props)

    // 直接使用编译好的 vnode.children 对象作为 slots 对象即可
    const slots = vnode.children || {}

    const instance = {
        state,
        // 将解析出的 props 数据包装为 shallowReactive 并定义到组件实例上
        props: shallowReactive(props),
        isMounted: false,
        subTree: null,
        slots,
        // 在组件实例中添加 mounted 数组，用来存储通过 onMounted 函数注册的生命周期钩子函数
        mounted: []
    }
    // 定义 emit 函数，它接收两个参数
    // event: 事件名称
    // payload: 传递给事件处理函数的参数
    function emit(event, ...payload) {
        // 根据约定对事件名称进行处理，例如 change --> onChange
        const eventName = `on${event[0].toUpperCase() + event.slice(1)}`
        // 根据处理后的事件名称去 props 中寻找对应的事件处理函数
        const handler = instance.props[eventName]
        if (handler) {
            // 调用事件处理函数并传递参数
            handler(...payload)
        } else {
            console.error('事件不存在')
        }
    }

    // 将 emit 函数添加到 setupContext 中，用户可以通过 setupContext 取得 emit 函数
    const setupContext = {
        attrs,
        emit,
        slots
    }
    // 在调用 setup 函数之前，设置当前组件实例
    setCurrentInstance(instance)
    // 调用 setup 函数，将只读版本的 props 作为第一个参数传递，避免用户意外地修改 props 的值，
    // 将 setupContext 作为第二个参数传递
    const setupResult = setup(shallowReadonly(instance.props), setupContext)
    // 在 setup 函数执行完毕之后，重置当前组件实例
    setCurrentInstance(null)
    // setupState 用来存储由 setup 返回的数据
    let setupState = null
    // 如果 setup 函数的返回值是函数，则将其作为渲染函数
    if (typeof setupResult === 'function') {
        // 报告冲突
        if (render) console.error('setup 函数返回渲染函数，render 选项将被忽略')
        // 将 setupResult 作为渲染函数
        render = setupResult
    } else {
        // 如果 setup 的返回值不是函数，则作为数据状态赋值给 setupState
        setupState = setupResult
    }

    vnode.component = instance

    // 在这里调用 created 钩子
    created && created.call(state)
    effect(() => {
        // 调用组件的渲染函数，获得子树
        const subTree = render.call(state, state)
        // 检查组件是否已经被挂载
        if (!instance.isMounted) {
            // 初次挂载，调用 patch 函数第一个参数传递 null
            patch(null, subTree, container, anchor)
            // 重点：将组件实例的 isMounted 设置为 true，这样当更新发生时就不会再次进行挂载操作，
            // 而是会执行更新
            instance.isMounted = true
            // 在这里调用 mounted 钩子
            // mounted && mounted.call(state)
            // 遍历 instance.mounted 数组并逐个执行即可
            instance.mounted && instance.mounted.forEach(hook => hook.call(renderContext))

        } else {
            // 在这里调用 beforeUpdate 钩子
            beforeUpdate && beforeUpdate.call(state)
            // 当 isMounted 为 true 时，说明组件已经被挂载，只需要完成自更新即可，
            // 所以在调用 patch 函数时，第一个参数为组件上一次渲染的子树，
            // 意思是，使用新的子树与上一次渲染的子树进行打补丁操作
            patch(instance.subTree, subTree, container, anchor)
            // 在这里调用 updated 钩子
            updated && updated.call(state)
        }
        // 更新组件实例的子树
        instance.subTree = subTree
    }, {
        scheduler: queueJob
    })
}

const renderContext = new Proxy(instance, {
    get(t, k, r) {
        const {
            state,
            props,
            slots
        } = t
        // 当 k 的值为 $slots 时，直接返回组件实例上的 slots
        if (k === '$slots') return slots

        if (state && k in state) {
            return state[k]
        } else if (k in props) {
            return props[k]
        } else if (setupState && k in setupState) {
            // 渲染上下文需要增加对 setupState 的支持
            return setupState[k]
        } else {
            console.error('不存在')
        }
    },
    set(t, k, v, r) {
        const {
            state,
            props
        } = t
        if (state && k in state) {
            state[k] = v
        } else if (k in props) {
            console.warn(`Attempting to mutate prop "${k}". Props are readonly.`)
        } else if (setupState && k in setupState) {
            // 渲染上下文需要增加对 setupState 的支持
            setupState[k] = v
        } else {
            console.error('不存在')
        }
    }
})

function patch(n1, n2, container, anchor) {
    if (n1 && n1.type !== n2.type) {
        unmount(n1)
        n1 = null
    }

    const {
        type
    } = n2

    if (typeof type === 'string') {
        // 省略部分代码
    } else if (type === Text) {
        // 省略部分代码
    } else if (type === Fragment) {
        // 省略部分代码
    } else if (typeof type === 'object') {
        // vnode.type 的值是选项对象，作为组件来处理
        if (!n1) {
            mountComponent(n2, container, anchor)
        } else {
            // 更新组件
            patchComponent(n1, n2, anchor)
        }
    }
}

// 用于在patch 中处理单个组件
function patchComponent(n1, n2, anchor) {
    // 获取组件实例，即 n1.component，同时让新的组件虚拟节点 n2.component 也指向组件实例
    const instance = (n2.component = n1.component)
    // 获取当前的 props 数据
    const {
        props
    } = instance
    // 调用 hasPropsChanged 检测为子组件传递的 props 是否发生变化，如果没有变化，则不需要更新
    if (hasPropsChanged(n1.props, n2.props)) {
        // 调用 resolveProps 函数重新获取 props 数据
        const [nextProps] = resolveProps(n2.type.props, n2.props)
        // 更新 props
        for (const k in nextProps) {
            props[k] = nextProps[k]
        }
        // 删除不存在的 props
        for (const k in props) {
            if (!(k in nextProps)) delete props[k]
        }
    }
}

function hasPropsChanged(
    prevProps,
    nextProps
) {
    const nextKeys = Object.keys(nextProps)
    // 如果新旧 props 的数量变了，则说明有变化
    if (nextKeys.length !== Object.keys(prevProps).length) {
        return true
    }
    // 只有
    for (let i = 0; i < nextKeys.length; i++) {
        const key = nextKeys[i]
        // 有不相等的 props，则说明有变化
        if (nextProps[key] !== prevProps[key]) return true
    }
    return false
}